おきらくPerlプログラミング入門 〜〜めざせ Perl マスター〜〜 広井 誠 最終回 ○タイ(tie) この講座も最終回となりましたが、最後は変数とクラスを結び付ける働きをす るタイ(tie) について説明します。タイは、リファレンスやオブジェクト指向と ともに、Perl 5 から追加された機能です。タイは、変数とユーザー定義のクラ スを結び付ける働きをし、変数の読み出しや書き込みのタイミングで、特定のメ ソッドを呼び出すことができます。変数には、スカラー、配列、ハッシュ、ファ イルハンドルを指定することができます。タイを使うことで、特定の変数を監視 するといったデバッグツールの作成や、データベースとハッシュを結び付けるこ とで、データベースへのアクセスを簡単に行うことができます。 ○スカラーとタイ まずは、スカラーから説明しましょう。変数とクラスを結び付けるには、関数 tie を使います。 tie variable, classname, list; 関数 tie は変数 variable をクラス classname に結び付けます。このように結 び付けられた変数をタイ変数といいます。tie は、クラス classname に定義さ れている特別なメソッドを呼び出します。これは変数の種類によって異なります。 変数の種類 |呼び出されるメソッド -----------------+-------------------- スカラー | TIESCALAR 配列 | TIEARRAY ハッシュ | TIEHASH ファイルハンドル | TIEHANDLE スカラーの場合は、TIESCALAR が呼び出されます。この時、tie に与えられた list が引数として渡されます。つまり、TIESCALAR は次のように呼び出されま す。 classname->TIESCALAR( list ); tie から呼び出されるこれらのメソッドは、オブジェクトを返さなければいけま せん。このオブジェクトと変数 [*1] が結び付けられます。 スカラーがタイ変数になると、変数にアクセスするたびに、次のメソッドが呼 び出されます。 アクション | 呼び出されるメソッド -------------+-------------------------- 読み出し | $obj->FETCH(); 書き込み | $obj->STORE( $new_value ); 廃棄 | $obj->DESTROY(); 廃棄には、関数 untie によってタイを解除する、関数 undef によって変数を未 定義にする、局所変数が有効範囲から出る(スコープの終了)ことの3通りがあ ります。変数がタイ変数となると、変数としての機能が失われる、つまり、値の 読み書きができなくなることに注意してください。値を保存したい場合は、オブ ジェクトにインスタンス変数を用意して、メソッド FETCH で変数の値を読み出 す、STORE で変数へ値を書き込む処理が必要になります。 それでは簡単な実行例として、スカラー変数のアクセスを監視するプログラム を作ってみましょう。最初にクラス MonScalar を定義します。 package MonScalar; sub TIESCALAR { my ($pkg, $name, $value) = @_; my $obj = { name => $name, value => $value }; bless $obj, $pkg; $obj; } sub FETCH { my $obj = shift; print 'Read : $', $obj->{'name'}, " -> $obj->{'value'}¥n"; $obj->{'value'}; } sub STORE { my ($obj, $new_value) = @_; print 'Write : $', $obj->{'name'}," <- $new_value¥n"; $obj->{'value'} = $new_value; } sub DESTROY { print "DESTORY¥n"; } TIESCALAR には引数として、変数名とその値を渡します。オブジェクトとして 無名のハッシュを生成して、変数名は name に、値は value にセットします。 後はオブジェクトをブレスして返すだけです。 FETCH は簡単です。オブジェクトから名前と値を取り出して print で出力し ます。FETCH の返り値がその変数の値として扱われるので、値をそのまま返しま す。STORE も簡単ですね。書き込まれる値が引数として渡されるので、それをオ ブジェクトの value に保存するだけです。 それでは実行してみましょう。 package main; $x = 10; tie $x, 'MonScalar', 'x', $x; $y = $x; print "y = $y¥n"; $x = 100; $z = $x; print "z = $z¥n"; untie $x; print "x = $x¥n"; 実行結果 Read : $x -> 10 y = 10 Write : $x <- 100 Read : $x -> 100 z = 100 DESTORY x = 100 最初に tie を使って変数 $x とクラス MonScalar を結び付けます。tie の返 り値は、TIESCALAR で返すオブジェクトです。このオブジェクトを使って特別な メソッド FETCH や STORE を呼び出すことができます。また、tied 関数を使っ て、変数に結び付けられたオブジェクトを取り出すこともできます。たとえば、 $x = 100; は (tied $x)->STORE( 100 ); と同じ動作になります。 この後 $x にアクセスするたびに、MonScalar のメソッド FETCH と STORE が 呼び出されます。untie を実行すると、メソッド DESTORY が呼び出されます。 ここで変数 $x の値に注意してください。参考文献によると、「タイされた変数 について untie 関数を呼び出すと、変数の値は元に復元される」とのことです。 ところが、$x は書き換えた値 100 になっています。どうやらスカラーの場合は、 元の値には復元されないようです。配列やハッシュでは、元の値に復元されます。 Windows で動作する ActivePerl 5.005 でも、スカラーの値は復元されませんが、 配列とハッシュの値は元に戻ります。スカラーでも元の値に復元される処理系が あるかもしれません。そこで、untie されると変数の値は復元されることを前提 に話を進めます。 [*1] 正確に説明すると、変数に格納されているデータ(スカラー、配列、ハッ シュやファイルハンドル)とオブジェクトが結び付けられます。変数自 身と結びつくわけではないことに注意してください。 ○変数の値を更新する 監視を止めると変数の値が元に戻るようでは、監視の役目を果たしているとは いえません。かえって、ユーザーを混乱させるだけです。変数の値は、オブジェ クトに格納されているので、監視を止める時に値を更新することにしましょう。 この処理をメソッド unmonitor で行うことにします。 [注意] X68k 版 Perl 5 では関数 tied を呼び出すことができないので(未 実装?)、これ以降のプログラムは ActivePerl 5.005 で動作チェッ クを行いました。 # 監視を止める sub unmonitor { my ($pkg, $rvar) = @_; my $obj = tied $$rvar; my $last_value = $obj->{'value'}; untie $$rvar; $$rvar = $last_value; } このメソッドは MonScalar->unmonitor( ¥$x ); と呼び出します。リファレンス を使って変数を渡すことに注意してください。関数 tied でオブジェクトを取り 出し、そこに格納されている値を $last_value にセットします。その後、untie でタイを解除してから、変数の値を $last_value に更新します。 これで正常に動作するように思いますが、実は不具合があるのです。-w オプ ションを付けて perl を実行すると、次のメッセージが表示されます。 untie attempted while 1 inner references still exist ... これは局所変数 $obj がタイ変数のオブジェクトを参照しているため、オブジェ クトのリファレンスカウントが 0 にならず、untie がオブジェクトを廃棄でき ないことを表しています。この問題は、局所変数 $obj の有効範囲を限定するこ とで解決することができます。 # 監視を止める(修正版) sub unmonitor { my ($pkg, $rvar) = @_; my $last_value; { my $obj = tied $$rvar; $last_value = $obj->{'value'}; $obj->{'name'} = '__UNMONITOR__' } untie $$rvar; $$rvar = $last_value; } 局所変数 $obj をブロック {} 内で定義します。こうすることで $obj の有効範 囲をブロック内に限定することができます。ブロックから抜けた時点で $obj は 無効となり、オブジェクトのリファレンスカウントが -1 されるため、untie で オブジェクトを廃棄することができます。 また、untie される時にメソッド DESTORY が呼び出されますが、変数の監視 を止めたわけですから、DESTROY のメッセージを出力しない方が良いでしょう。 そこで、オブジェクトの name に __UNMONITOR__ をセットし、DESTROY でチェッ クすることにします。 # 修正版 sub DESTROY { my $obj = shift; if( $obj->{'name'} ne '__UNMONITOR__' ){ print 'DESTORY $', "$obj->{'name'}¥n"; } } プログラムの修正は簡単ですね。オブジェクトから name の値を取り出し、それ を __UNMONITOR__ と比較するだけです。違っていれば、メッセージを出力しま す。 それでは、untie を unmonitor に変更して、実行してみましょう。 $x = 10; tie $x, 'MonScalar', 'x', $x; $y = $x; print "y = $y¥n"; $x = 100; $z = $x; print "z = $z¥n"; MonScalar->unmonitor( ¥$x ); print "x = $x¥n"; 実行結果 Read : $x -> 10 y = 10 Write : $x <- 100 Read : $x -> 100 z = 100 x = 100 監視を止めた後でも $x の値が 100 となります。正常に動作していますね。 ○配列とタイ 次は配列です。配列にタイを適用する場合、個々の要素へのアクセスの他にも、 push, pop など関数を使ったアクセスがあります。ところが、現在のところ配列 のタイでサポートされている操作は、要素の読み書きのみです。まあ、近い将来 に改善されると思います。 配列の場合、tie が実行されると TIEARRAY が呼び出されます。そして、各要 素にアクセスするたびに、次のメソッドが呼び出されます。 アクション | 呼び出されるメソッド ------------+------------------------------------ 読み出し | $obj->FETCH( $index ); 書き込み | $obj->STORE( $index, $new_value ); 廃棄 | $obj->DESTROY(); メソッドの名前はスカラーと同じですが、アクセスした要素の添字が渡されるこ とに注意してください。 それでは簡単な実行例として、配列のアクセスを監視するプログラムを作って みましょう。最初にパッケージ MonArray を定義します。 package MonArray; sub TIEARRAY { my ($pkg, $name, $ra) = @_; my $obj = { name => $name, array => [@$ra], }; bless $obj, $pkg; $obj; } sub FETCH { my ($obj, $index) = @_; my $value = $obj->{'array'}->[$index]; print 'Read : $',$obj->{'name'}, "[$index] -> $value¥n"; $value; } sub STORE { my ($obj, $index, $new_value) = @_; print 'Write : $',$obj->{'name'}, "[$index] <- $new_value¥n"; $obj->{'array'}->[$index] = $new_value; } sub DESTROY { my $obj = shift; if( $obj->{'name'} ne '__UNMONITOR__' ){ print 'DESTORY @', "$obj->{'name'}¥n"; } } sub unmonitor { my ($pkg, $rvar) = @_; my $last_array; { my $obj = tied @$rvar; $last_array = $obj->{'array'}; $obj->{'name'} = '__UNMONITOR__' } untie @$rvar; @$rvar = @$last_array; } スカラーと同様に、TIEARRAY には配列名と配列そのものを渡します。配列を 渡す時はリファレンスを使った方が簡単です。次にオブジェクトを生成し、名前 を name に、配列は無名の配列にコピーして array にセットします。後はオブ ジェクトをブレスして返すだけです。FETCH と STORE は簡単です。引数として 渡された添字で、オブジェクトに格納された無名の配列にアクセスすればいいわ けです。監視を止める場合は、スカラーと同様に unmonitor を使います。オブ ジェクトに格納されている配列を、監視していた配列に代入するだけです。 それでは実行してみましょう。 package main; @a = (10, 20, 30); tie @a, 'MonArray', 'a', ¥@a; $x = $a[1]; print "x = $x¥n"; $a[1] = 200; $y = $a[1]; print "y = $y¥n"; MonArray->unmonitor( ¥@a ); print "@a¥n"; 実行結果 Read : $a[1] -> 20 x = 20 Write : $a[1] <- 200 Read : $a[1] -> 200 y = 200 10 200 30 最初に tie を使って配列 @a とクラス MonArray を結び付けます。この後 @a の要素にアクセスすると、MonArray のメソッド FETCH と STORE が呼び出され ます。unmonitor で監視を止めた後でも、配列の値はきちんと更新されています ね。 ○ハッシュとタイ 配列の場合と異なり、タイによるハッシュへのアクセスは、個々の要素へのア クセス、ハッシュ全体の操作、関数による操作の全てを完全にサポートしていま す。 ハッシュの場合、tie が実行されると TIEHASH が呼び出されます。そして、 ハッシュにアクセスするたびに、次のメソッドが呼び出されます。 操作例 | 呼び出されるメソッド -------------------+---------------------- $h{a}; | $obj->FETCH('a'); $h{a} = 1; | $obj->STORE('a', 1); delete $h{a}; | $obj->DELETE('a'); exists $h{a}; | $obj->EXISTS('a'); %h = (); | $obj->CLEAR(); | %h = ( a => 1 ); | $obj->CLEAR(); | $obj->STORE('a'); | keys, values, each | $lk = $obj->FIRSTKEY(); | do { | $val = $obj->FETCH($lk); | } while ( $lk = $obj->NEXTKEY($lk) ); FIRSTKEY は最初のキーを、NEXTKEY は次のキーを返すように作る必要があり ます。keys ではこの2つのメソッドが呼び出され、values や each では、各々 のキーについて FETCH が呼び出されます。 定義するメソッドが多くで面倒だと感じる方は、Tie::Hash, Tie::StdHash と いう、タイハッシュに対する基本クラスを定義したモジュールが標準ライブラリ に用意されているので、これを継承するといいでしょう。 それでは簡単な実行例として、ハッシュのアクセスを監視するプログラムを作っ てみましょう。簡単な例題ということで、要素へのアクセスとハッシュ全体をク リアする操作だけを監視することにします。最初にパッケージ MonHash を定義 します。 package MonHash; sub TIEHASH { my ($pkg, $name, $rh) = @_; my $obj = { name => $name, hash => {%$rh}, }; bless $obj, $pkg; $obj; } sub FETCH { my ($obj, $index) = @_; my $value = $obj->{'hash'}->{$index}; print 'Read : $', $obj->{'name'}, "{$index} -> $value¥n"; $value; } sub STORE { my ($obj, $index, $new_value) = @_; print 'Write : $', $obj->{'name'}, "{$index} <- $new_value¥n"; $obj->{'hash'}->{$index} = $new_value; } sub CLEAR { my $obj = shift; print 'Clear : %', "$obj->{'name'}¥n"; $obj->{'hash'} = {}; } sub DESTROY { my $obj = shift; if( $obj->{'name'} ne '__UNMONITOR__' ){ print 'DESTORY %', "$obj->{'name'}¥n"; } } sub unmonitor { my ($pkg, $rvar) = @_; my $last_hash; { my $obj = tied %$rvar; $last_hash = $obj->{'hash'}; $obj->{'name'} = '__UNMONITOR__' } untie %$rvar; %$rvar = %$last_hash; } スカラーや配列と同様に、TIEHASH にはハッシュ名とハッシュそのものを渡し ます。ハッシュを渡す時はリファレンスを使います。次にオブジェクトを生成し、 名前を name に、ハッシュの値は無名のハッシュにコピーして hash にセットし ます。後はオブジェクトをブレスして返すだけです。FETCH と STORE は簡単で す。引数として渡された添字で、オブジェクトに格納された無名のハッシュにア クセスすればいいわけです。CLEAR はもっと簡単ですね。メッセージを出力した ら、ハッシュを空にするだけです。unmonitor も配列をハッシュに変えただけで す。 それでは実行してみましょう。 package main; %h = (a => 10, b => 20, c => 30); print %h, "¥n"; tie %h, 'MonHash', 'h', ¥%h; $x = $h{'b'}; print "x = $x¥n"; $h{'b'} = 200; $y = $h{'b'}; print "y = $y¥n"; %h = ( a => 100, b => 200, c => 300 ); MonHash->unmonitor( ¥%h ); print %h; 実行結果 a10b20c30 Read : $h{b} -> 20 x = 20 Write : $h{b} <- 200 Read : $h{b} -> 200 y = 200 Clear : %h Write : $h{a} <- 100 Write : $h{b} <- 200 Write : $h{c} <- 300 a100b200c300 もう説明しなくてもいいですね。正常に動作しています。 ○パッケージ Monitor の作成 ここまで、スカラー、配列、ハッシュの監視プログラムを作りましたが、いち いちクラスを指定して tie 関数を呼び出すのは面倒ですね。そこで、与えられ た変数の種類を調べて、適切な tie 関数を呼び出すプログラムを作りましょう。 作成するプログラムは、変数を監視する monitor と監視を止める unmonitor の 2つです。monitor は変数のリファレンスと名前を、unmonitor は変数のリファ レンスを引数として受け取ります。 # 使用例 $x = 100; monitor( ¥$x, 'x' ); unmonitor( ¥$x ); monitor と unmonitor はパッケージ Monitor に定義します。プログラムは次 のようになります。 # パッケージの定義(Monitor.pm) package Monitor; use Exporter; @ISA = (Exporter); @EXPORT_OK = ('monitor', 'unmonitor'); # 変数を監視する sub monitor { my ($rvar, $name) = @_; my $type = ref( $rvar ); # 型のチェック if( $type eq 'SCALAR' ){ tie $$rvar, 'MonScalar', $name, $$rvar; } elsif( $type eq 'ARRAY' ){ tie @$rvar, 'MonArray', $name, $rvar; } elsif( $type eq 'HASH' ){ tie %$rvar = 'MonHash', $name, $rvar; } else { print STDERR "リファレンスが必要です¥n"; } } # 変数の監視を止める sub unmonitor { my $rvar = shift; my $type = ref( $rvar ); # 型のチェック if( $type eq 'SCALAR' ){ MonScalar->unmonitor( $rvar ); } elsif( $type eq 'ARRAY' ){ MonArray->unmonitor( $rvar ); } elsif( $type eq 'HASH' ){ MonHash->unmonitor( $rvar ); } else { print STDERR "リファレンスが必要です¥n"; } } 2つのプログラムともに、関数 ref を呼び出してリファレンス先のデータの種 類を調べ、適切な関数を呼び出すだけです。MonScalar, MonArray, MonHash は ファイル Monitor.pm にまとめて定義しておきます。 それでは実行例を示します。 # テストプログラム(montest.pl) use Monitor ('monitor', 'unmonitor'); $x = 10; @a = (100, 200, 300); %h = (a => 1, b => 2, c => 3 ); monitor( ¥$x, 'x' ); monitor( ¥@a, 'a' ); monitor( ¥%h, 'h' ); $x = 20; $y1 = $x; print "y1 = $y1¥n"; $a[2] = 3000; $y2 = $a[2]; print "y2 = $y2¥n"; $h{'c'} = 30; $y3 = $h{'c'}; print "y3 = $y3¥n"; unmonitor( ¥$x ); unmonitor( ¥@a ); unmonitor( ¥%h ); print "x = $x¥n"; print "a = @a¥n"; print "h = ", %h, "¥n"; 実行結果 Write : $x <- 20 Read : $x -> 20 y1 = 20 Write : $a[2] <- 3000 Read : $a[2] -> 3000 y2 = 3000 Write : $h{c} <- 30 Read : $h{c} -> 30 y3 = 30 x = 20 a = 100 200 3000 h = a1b2c30 正常に動作していますね。 タイの最も有効な利用方法は、ハッシュとデータベースを結び付けることです が、本講座の範囲を超えるので説明を割愛いたします。また、ファイルハンドル とタイの説明も割愛させていただきます。興味のある方は参考文献を読んでくだ さい。 ○おわりに 最近、インタプリタ形式のプログラミング言語(スクリプト言語ともいう)が 注目を集めています。その中で、Perl は CGI スクリプトを書くためのプログラ ミング言語として、その普及度は目を見張るものがあります。この講座では、筆 者のスキル不足のため、CGI プログラミングを取り上げることはできませんでし た。しかしながら、Perl の基本からオブジェクト指向まで、プログラミング言 語としての機能は一通り説明できたと思っています。 Perl は応用範囲の広いプログラミング言語です。CGI やネットワークの他に も、GUI ツールキット Tk を利用するためのモジュール Tk.pm [*2] をロードす ることで、Perl でも GUI アプリケーションを作成することができます。これか らも、いろいろな分野で Perl が利用されることでしょう。皆さんも Perl を使っ てプログラミングを楽しんでください。 最後になりましたが、この講座が少しでも皆様のお役に立てれば、筆者として はこれほどの幸せはありません。長い間お付き合いいただいた読者の皆様、なら びに編集スタッフの方々に感謝の意を表します。 [*2] Perl と Tk.pm の組み合わせを「Perl/Tk」といいます。なお、 Perl/Tk を実行するために Tcl/Tk は必要ありません。use Tk; で Tk の利用が可能になります。 Perl のモジュールは総合 Perl アーカイブネットワーク (CPAN) からダウンロードすることができます。ActivePerl 用 Tk.pm の入手 先は、須栗歩人氏のホームページ http://members.xoom.com/tcltk/index.html を参照してください。Tcl/Tk や Perl/Tk の情報がとても参考になり ます。 ―参考文献― [1] Larry Wall, Tom Christiansen, Randal L. Schwartz 共著「プログラミン グPerl」改訂版 オライリー・ジャパン 1997 [2] Sriram Srinivasan 著「実用Perlプログラミング」オライリー・ジャ パン 1998 (EOF)